import logging
import urlparse
import urllib2
import time

from pyjenkinsci.jenkinsobject import jenkinsobject
from pyjenkinsci.build import build
from pyjenkinsci.exceptions import NoBuildData

log = logging.getLogger(__name__)

class job( jenkinsobject ):
    """
    Represents a hudson environment.
    """    
    def __init__( self, url, name, hudson ):
        self.name = name
        self.hudson = hudson
        jenkinsobject.__init__( self, url )
        
    def id( self ):
        return self._data["name"]
    
    def __str__(self):
        return self._data["name"]
    
    def getHudsonObject(self):
        return self.hudson
    
    def getBuildTriggerUrl( self, token=None ):
        if token==None:
            extra = "build"
        else:
            assert isinstance(token, str ), "token if provided should be a string."
            extra = "build?token=%s" % token

        buildurl = urlparse.urljoin( self.baseurl, extra )
        return buildurl
    
    def hit_url(self, url ):
        fn_urlopen = self.getHudsonObject().getOpener()
        
        try:
            stream = fn_urlopen( url )
            html_result = stream.read()
            
        except urllib2.HTTPError, e:
            log.debug( "Error reading %s" % url )
            raise
        return html_result
    
    def invoke( self, securitytoken=None, block=False, skip_if_running=False, invoke_pre_check_delay=3, invoke_block_delay=15 ):
        assert isinstance( invoke_pre_check_delay, (int, float) )
        assert isinstance( invoke_block_delay, (int, float) )
        assert isinstance( block, bool )
        assert isinstance( skip_if_running, bool )
        
        skip_build = False
        
        if self.is_queued():
            log.warn( "Will not request new build because %s is already queued" % self.id() )
            skip_build = True
        elif self.is_running():
            if skip_if_running:
                log.warn( "Will not request new build because %s is already running" % self.id() )
                skip_build = True
            else:
                log.warn("Will re-schedule %s even though it is already running" % self.id() )
        
        original_build_no = self.getLastBuildNumber()
         
        if skip_build:
            pass
        else:
            log.info( "Attempting to start %s on %s" % ( self.id(), repr(self.getHudsonObject()) ) )
            url = self.getBuildTriggerUrl( securitytoken )
            html_result = self.hit_url( url )
            assert len( html_result ) > 0        

        if invoke_pre_check_delay > 0:
            log.info("Waiting for %is to allow Hudson to catch up" % invoke_pre_check_delay )
            time.sleep( invoke_pre_check_delay )
            
        if block:
            total_wait = 0
            while self.is_queued():
                log.info( "Waited %is for %s to begin..." % ( total_wait, self.id() ) )
                time.sleep( invoke_block_delay )
                total_wait += invoke_block_delay
            if self.is_running():
                running_build = self.getLastBuild()
                running_build.block_until_complete( delay=invoke_pre_check_delay )
                assert running_build.is_good()
            else:
                assert self.getLastBuildNumber() > original_build_no, "Job does not appear to have run."
        else:
            if self.is_queued():
                log.info( "%s has been queued." % self.id() )
            elif self.is_running():
                log.info( "%s is running." % self.id() )
            elif original_build_no < self.getLastBuildNumber():
                log.info( "%s has completed." % self.id() )
            else:
                raise AssertionError("The job did not schedule.")
            
    def getLastGoodBuildNumber( self ):
        """
        Get the numerical ID of the last good build.
        """
        bd = self._data["lastSuccessfulBuild"]
        buildid = bd[ "number" ]
        assert type( buildid ) == int, "Build ID should be an integer, got %s" % repr( buildid )
        return buildid
    
    def getLastBuildNumber( self ):
        """
        Get the numerical ID of the last build.
        """
        bd = self._data["lastBuild"]
        buildid = bd[ "number" ]
        assert type( buildid ) == int, "Build ID should be an integer, got %s" % repr( buildid )
        return buildid
    
    def getLastCompletedBuildNumber( self ):
        """
        Get the numerical ID of the last complete build.
        """
        bd = self._data["lastCompletedBuild"]
        buildid = bd[ "number" ]
        assert type( buildid ) == int, "Build ID should be an integer, got %s" % repr( buildid )
        return buildid
        
    def getBuildDict(self):
        if not self._data.has_key( "builds" ):
            raise NoBuildData( repr(self) )
        
        return dict( ( a["number"], a["url"] ) for a in self._data["builds"] )
    
    def getBuildIds(self):
        """
        Return a sorted list of all good builds as ints.
        """
        return reversed( sorted( self.getBuildDict().keys() ) )
    
    def getLastGoodBuild( self ):
        """
        Get the last good build
        """
        bn = self.getLastGoodBuildNumber()
        return self.getBuild( bn )
    
    def getLastBuild( self ):
        """
        Get the last good build
        """
        bn = self.getLastBuildNumber()
        return self.getBuild( bn )
    
    def getLastCompletedBuild( self ):
        """
        Get the last build regardless of status
        """
        bn = self.getLastCompletedBuildNumber()
        return self.getBuild( bn )
    
    def getBuild( self, buildnumber ):
        assert type(buildnumber) == int
        url = self.getBuildDict()[ buildnumber ]
        return build( url, buildnumber, job=self )
    
    def __getitem__( self, buildnumber ):
        return self.getBuild(buildnumber)
    
    def is_queued_or_running(self):
        return self.is_queued() or self.is_running()
    
    def is_queued(self):
        self.poll()
        return self._data["inQueue"]
        
    def is_running(self):
        self.poll()
        
        try:
            return self.getLastBuild().is_running()
        except NoBuildData:
            log.info("No build info available for %s, assuming not running." % str(self) )
            return False